/* registerplot.cpp
 * Class CurveData
 * Class RegisterPlot inherits QwtPlot
 * Class TimeScaleDraw inherits QwtScaleDraw
 * Class Destructor inherits QObject
 * The class that actually does the hard part of the graphing
 * Main code based off the Qwt example of a constantly updating plot
 * TimeScaleDraw allows the x-axis to display times as we are used to
 * CurveData is a convenience class used to store the data behind our plots
 * Destructor is a convenience class used to delete curve objects at a later time
 */

#include <qwt_plot_canvas.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <qwt_legend_item.h>
#include <qwt_scale_engine.h>

#if QT_VERSION >= 0x040000
#include <qpaintengine.h>
#endif

#include <QDebug>
#include <QDateTime>
#include <math.h>

#include "registerplot.h"

CurveData::CurveData(int maxSize):
    d_count(0)
{
    //Only store data up to a maximum size
    d_maxsize = maxSize;
    d_x.resize(d_maxsize);
    d_y.resize(d_maxsize);
}

void CurveData::append(double *x, double *y, int count)
{
    //Are we full yet? If yes, rotate the values; if no, just add them on the end
    if ( (d_count + count) > ( d_maxsize ) ) {
        for ( register int i = 0; i < (d_count - count); i++ )
        {
            d_x[i] = d_x[i + count];
            d_y[i] = d_y[i + count];
        }
        d_count = d_maxsize - count;

        for ( register int i = 0; i < count; i++ )
        {
            d_x[d_count + i] = x[i];
            d_y[d_count + i] = y[i];
        }
        d_count += count;

    } else {
        for ( register int i = 0; i < count; i++ )
        {
            d_x[d_count + i] = x[i];
            d_y[d_count + i] = y[i];
        }
        d_count += count;
    }
}

int CurveData::count() const
{
    return d_count;
}

int CurveData::size() const
{
    return d_x.size();
}

const double *CurveData::x() const
{
    return d_x.data();
}

const double *CurveData::y() const
{
    return d_y.data();
}

Destructor::Destructor(QwtPlotCurve *item, CurveData *data)
{
    m_item = item;
    m_data = data;
}

Destructor::~Destructor()
{
    //Simply delays the detaching and deletion of the objects
    m_item->detach();
    delete m_item;
    delete m_data;
}

RegisterPlot::RegisterPlot(int curveSize, QWidget *parent):
    QwtPlot(parent)
{
    m_maxCurveSize = curveSize;
    init();
}

RegisterPlot::~RegisterPlot()
{
    //Delete all that memory we allocated
    for (register int i=0; i < d_data->count(); ++i)
        delete d_data->value(i);

    delete d_data;
    d_data = NULL;
}

void RegisterPlot::init() {
    d_data = new QList<CurveData*>();
    d_curve = new QList<QwtPlotCurve*>();
    d_legend = new QwtLegend(this);

    //The plot takes ownership of the legend
    insertLegend(d_legend, QwtPlot::BottomLegend);
    legend()->setItemMode(QwtLegend::ClickableItem);

    //The plot takes ownership of the scale draw
    d_scale = new TimeScaleDraw();
    setAxisScaleDraw(2, d_scale);

    setAutoReplot(false);
}

void RegisterPlot::appendData(double x, double y, int curve)
{
    appendData(&x, &y, 1, curve);
}

void RegisterPlot::appendData(double *x, double *y, int size, int curve)
{
    //Main repainting logic - add the data to our curves, then repaint them
    if ( d_data->isEmpty() || d_curve->isEmpty())
        addCurve("default", Qt::black);

    d_data->value(curve)->append(x, y, size);
    d_curve->value(curve)->setRawData(d_data->value(curve)->x(), d_data->value(curve)->y(), d_data->value(curve)->count());
#ifdef __GNUC__
#endif

    const bool cacheMode = 
        canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached);

#if QT_VERSION >= 0x040000 && defined(Q_WS_X11)
    // Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent 
    // works on X11. This has an tremendous effect on the performance..

    canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true);
#endif

    canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
    d_curve->value(curve)->draw(d_curve->value(curve)->dataSize() - size, d_curve->value(curve)->dataSize() - 1);
    canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode);

#if QT_VERSION >= 0x040000 && defined(Q_WS_X11)
    canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, false);
#endif
}

void RegisterPlot::removeData()
{
    //Delete all that memory we allocated
    for (register int i=0; i < d_curve->count(); ++i) {
        d_curve->at(i)->detach();
        delete d_curve->at(i);
    }

    delete d_curve;

    for (register int i=0; i < d_data->count(); ++i)
        delete d_data->at(i);

    delete d_data;

    replot();
}

void RegisterPlot::addCurve(const char * name, QColor color)
{
    //Add a curve of the specified colour
    d_data->append(new CurveData(m_maxCurveSize));
    d_curve->append(new QwtPlotCurve(name));
    d_curve->last()->setStyle(QwtPlotCurve::Lines);
    d_curve->last()->setPaintAttribute(QwtPlotCurve::PaintFiltered);
    d_curve->last()->setPen(QPen(color));
    d_curve->last()->attach(this);
}

void RegisterPlot::removeCurve(QwtPlotItem* item)
{
    //Remove a curve based on its name
    for (register int i=0; i < d_curve->count(); ++i) {
        QwtPlotCurve *curve = d_curve->at(i);
        if ( curve->title().text() == item->title().text()) {
            curve->setVisible(false);
            Destructor *des = new Destructor(curve, d_data->at(i));
            //This deletes the objects once control returns to the event loop
            // this function depends on the curve's existence, so it cannot be detached or deleted here
            // which is why we need the destructor convenience class
            des->deleteLater();
            d_curve->removeAt(i);
            d_data->removeAt(i);
            break;
        }
    }
}

int RegisterPlot::count()
{
    //Return the size of the first curve, which should always be the longest running one
    return d_data->at(0)->count();
}

TimeScaleDraw::TimeScaleDraw():
    QwtScaleDraw()
{
}

QwtText TimeScaleDraw::label(double v) const
{
    //Convert a double to a time string based on 0-1 equals one full day
    v -= floor(v);
    int hours = (int)floor(v*24);
    int minutes = ((int)floor(v*24*60)) % 60;
    int seconds = ((int)floor(v*24*60*60)) % 60;
    QString label = QString("%1:%2:%3").arg(hours, 2, 'g', 10, '0').arg(minutes, 2, 'g', 10, '0').arg(seconds, 2, 'g', 10, '0');
    QwtText text = QwtText(label, QwtText::PlainText);
    return text;
}
